package cn.erbudev.model;

import cn.erbudev.util.DrawUtil;

import java.util.*;

public class DrawEntityCombination {
    private List<DrawEntity> entitiesList;

    private List<Integer> entitiesIDList;
    private Map<Integer, DrawEntity> entityMap;

    public DrawEntityCombination(List<DrawEntity> entitiesList) {
        this.entitiesList = new ArrayList<>();
        this.entitiesIDList = new LinkedList<>();
        this.entityMap = new HashMap<>();
        int num = 0;
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < entitiesList.size(); i++) {
            DrawEntity entity = entitiesList.get(i);
            if (entity.getEntityLength() > DrawUtil.Error) {
                this.entitiesList.add(entity);
                entitiesIDList.add(i);
                entity.setID(i);
                entityMap.put(i, entity);
                map.put(i, num);
                num++;
            } else {
                Point startPoint = entity.getStartPoint();
                Point endPoint = entity.getEndPoint();
                int index1 = (i - 1 + entitiesList.size()) % entitiesList.size();
                int index2 = (i + 1) % entitiesList.size();
                DrawEntity lastEntity = entitiesList.get(index1);
                DrawEntity nextEntity = entitiesList.get(index2);
                Point newPoint = new Point((startPoint.getX() + endPoint.getX()) / 2.0, (startPoint.getY() + endPoint.getY()) / 2.0);
                entitiesList.set(index1, lastEntity.reduce(newPoint, false));
                entitiesList.set(index2, nextEntity.reduce(newPoint, true));
                if (map.containsKey(index1)) {
                    this.entitiesList.set(map.get(index1), entitiesList.get(index1));
                }
                if (map.containsKey(index2)) {
                    this.entitiesList.set(map.get(index2), entitiesList.get(index2));
                }
            }
        }
    }

    public double getAngle(Point start, Point end) {
        double x = end.getX() - start.getX();
        double y = end.getY() - start.getY();
        double angle = Math.atan(y / x);
        if (angle < 0)
            angle += Math.PI;
        if (y < -DrawUtil.Error)
            angle += Math.PI;
        else if (Math.abs(y) < DrawUtil.Error) {
            if (x > -DrawUtil.Error)
                angle = 0;
            else
                angle = Math.PI;

        }
        return angle;
    }

    public boolean addEntity(DrawEntity entity) {
        return entitiesList.add(entity);
    }

    private boolean inTheSameDirectionLine(Line line1, Line line2) {
        double angle1 = getAngle(new Point(0, 0), line1.getDirection());
        double angle2 = getAngle(new Point(0, 0), line2.getDirection());
        return Math.abs(angle1 - angle2) < DrawUtil.Error;
    }

    private boolean inTheSameDirectionArc(Arc arc1, Arc arc2) {
        double angle1 = arc1.getAngleDelta();
        double angle2 = arc2.getAngleDelta();
        return (angle1 - angle2) < DrawUtil.Error;

    }

    private Map<Integer, List<Point>> creatChildEntityPoints(double d, List<Integer> entitiesIDListNew) {
        Map<Integer, List<Point>> result = new HashMap<>();
//        Set<Integer> visitedIDSet = new HashSet<>();
        int i = 0;
        while (entitiesIDListNew.size() > 1 && !result.containsKey(entitiesIDListNew.get(i % entitiesIDListNew.size())) && i < entitiesIDListNew.size()) {
            DrawEntity startEntity = entityMap.get(entitiesIDListNew.get(i));
            DrawEntity endEntity = entityMap.get(entitiesIDListNew.get((i + 1) % entitiesIDListNew.size()));
            List<Point> crossPoints = startEntity.getChildCrossPoints(endEntity, d);
            if (crossPoints.size() != 0) {
//                boolean flag = true;
//                if (crossPoints.size() == 1) {
//                    flag = pointWithinEntity(crossPoints.get(0));
//                } else if (crossPoints.size() == 3) {
//                    flag = pointWithinEntity(crossPoints.get(1)) && pointWithinEntity(crossPoints.get(2));
//                }
//                if (flag) {
                    result.put(entitiesIDListNew.get(i), crossPoints);
                    i++;
//                } else {
//                    entitiesIDListNew.remove(i);
//                }

            } else {
//                System.out.println(i);
//                int id1 = entitiesIDListNew.get(i);
//                int id2 = entitiesIDListNew.get((i + 1) % entitiesIDListNew.size());
//                int id3 = entitiesIDListNew.get((i - 1 + entitiesIDListNew.size()) % entitiesIDListNew.size());
//                entitiesIDListNew.remove(i);
//                entitiesIDListNew.remove(i % entitiesIDListNew.size());
//                result.remove(id1);
//                result.remove(id2);
//                i = entitiesIDListNew.indexOf(id3);
//                result.remove(id3);
                int id1 = entitiesIDListNew.get(i);
                int id3 = entitiesIDListNew.get((i - 1 + entitiesIDListNew.size()) % entitiesIDListNew.size());
                entitiesIDListNew.remove(i);
                result.remove(id1);
                i = entitiesIDListNew.indexOf(id3);
                result.remove(id3);
            }
        }
        return result;
    }


    public List<List<DrawEntity>> getChildEntities(double d) {
        List<Integer> entitiesIDListNew = new LinkedList<>(entitiesIDList);
        Map<Integer, List<Point>> childEntityPoints = creatChildEntityPoints(d, entitiesIDListNew);
        if (childEntityPoints.size() <= 1 && entitiesIDListNew.size() <= 1)
            return new ArrayList<>();

        Point junction;
        Point endJunc;
        List<DrawEntity> result = new ArrayList<>();
        List<Point> tempJuncList = childEntityPoints.get(entitiesIDListNew.get(entitiesIDListNew.size() - 1));
        switch (tempJuncList.size()) {
            case 1:
                junction = tempJuncList.get(0);
                endJunc = tempJuncList.get(0);
                break;
            case 3:
                junction = tempJuncList.get(2);
                endJunc = tempJuncList.get(2);
                break; //可选
            default: //可选
                return new ArrayList<>();
        }
        Set<Integer> indexSet = new HashSet<>();
        for (int i = 0; i < entitiesIDListNew.size(); i++) {
            List<Point> juncList = childEntityPoints.get(entitiesIDListNew.get(i));
            DrawEntity entityORIG = entityMap.get(entitiesIDListNew.get(i));
            switch (juncList.size()) {
                case 1:
                    if (entityORIG.getType() == DrawEntity.LINE) {
                        Line lineNew = new Line(junction, juncList.get(0));
                        result.add(lineNew);
                        if (!inTheSameDirectionLine(lineNew, (Line) entityORIG))
                            indexSet.add(i);
                        junction = juncList.get(0);
                    } else if (entityORIG.getType() == DrawEntity.ARC) {
                        Arc arcORIG = (Arc) entityORIG;
                        Arc arcNew = new Arc(arcORIG.getCentre(), junction, juncList.get(0));
                        result.add(arcNew);
                        if (!inTheSameDirectionArc(arcNew, arcORIG))
                            indexSet.add(i);
                        junction = juncList.get(0);
                    }
                    break;
                case 3:
                    if (entityORIG.getType() == DrawEntity.LINE) {
                        Line line = new Line(junction, juncList.get(1));
                        result.add(line);
                        if (!inTheSameDirectionLine(line, (Line) entityORIG))
                            indexSet.add(i);
                        junction = juncList.get(2);
                        result.add(new Arc(juncList.get(0), juncList.get(1), juncList.get(2)));
                    } else if (entityORIG.getType() == DrawEntity.ARC) {
                        Arc arcORIG = (Arc) entityORIG;
                        Arc arcNew = new Arc(arcORIG.getCentre(), junction, juncList.get(1));
                        result.add(arcNew);
                        if (!inTheSameDirectionArc(arcNew, arcORIG))
                            indexSet.add(i);
                        junction = juncList.get(2);
                        result.add(new Arc(juncList.get(0), juncList.get(1), juncList.get(2)));
                    }
                    break;
                default: //可选
                    return new ArrayList<>();
            }
        }
        if (!indexSet.isEmpty()) {
            List<Integer> idListNew = new LinkedList<>();
            for (int i = 0; i < entitiesIDListNew.size(); i++) {
                if (!indexSet.contains(i)) {
                    idListNew.add(entitiesIDListNew.get(i));
                }
            }
            entitiesIDList = new LinkedList<>(idListNew);
            return getChildEntities(d);
        }


        for (int index = 0; index < result.size(); index++) {
            DrawEntity entity = result.get(index);
            entity.setID(index);
        }


        List<FissionPoint> breakpointsList = new ArrayList<>();
        for (int index = 0; index < result.size(); index++) {
            DrawEntity start = result.get(index);
            for (int i = index + 2; i < index + result.size() - 1 && i < result.size(); i++) {
                DrawEntity end = result.get(i);
                List<Point> intersectionPoint = start.getIntersection(end);
                if (intersectionPoint != null && intersectionPoint.size() != 0) {
                    breakpointsList.add(new FissionPoint(start.getID(), end.getID(), intersectionPoint));
                    System.out.println(start.getID() + "=======" + end.getID());
                }
            }
        }
        List<List<DrawEntity>> resultFission = new ArrayList<>();
        resultFission.add(result);
        for (FissionPoint fission : breakpointsList) {
            List<DrawEntity> target = new ArrayList<>();
            Iterator<List<DrawEntity>> iter = resultFission.iterator();
            while (iter.hasNext()) {
                List<DrawEntity> temp = iter.next();
                Set<Integer> idSet = new HashSet<>();
                for (DrawEntity e : temp) {
                    idSet.add(e.getID());
                }
                for (int id : idSet) {
                    System.out.print(id + " ");
                }
                if (idSet.contains(fission.getID1()) && idSet.contains(fission.getID2())) {
                    target = temp;
                    iter.remove();
                    break;
                }
            }

            if (target.size() != 0) {
                List<List<DrawEntity>> r = fission(target, fission);
                resultFission.addAll(r);
            }
        }

        Iterator<List<DrawEntity>> iter = resultFission.iterator();
        while (iter.hasNext()) {
            List<DrawEntity> temp = iter.next();
            Set<Point> pointSet = new HashSet<>();
            pointSet.add(temp.get(0).getStartPoint());
            for (DrawEntity e : temp) {
                pointSet.add(e.getEndPoint());
            }
            boolean flag = true;
            for (Point point : pointSet) {
                flag = flag && pointWithinEntity(point);
                if (!flag) {
                    iter.remove();
                    break;
                }
            }
        }


        return resultFission;
    }

    public boolean isQualified(DrawEntityCombination father) {
        return true;
    }


    //图元组合发生裂变，将生成两个图元组合
    private List<List<DrawEntity>> fission(List<DrawEntity> list, FissionPoint fission) {
        List<List<DrawEntity>> result = new ArrayList<>();
        int id1 = fission.getID1();
        int id2 = fission.getID2();
        int index1 = Integer.MAX_VALUE;
        int index2 = Integer.MAX_VALUE;
        for (int i = 0; i < list.size(); i++) {
            DrawEntity entity = list.get(i);
            if (entity.getID() == id1) {
                index1 = i;
            }
            if (entity.getID() == id2) {
                index2 = i;
            }
        }
        if (index1 == Integer.MAX_VALUE || index2 == Integer.MAX_VALUE) {
            result.add(list);
            return result;
        }
        int minIndex = Integer.min(index1, index2);
        int maxIndex = Integer.max(index1, index2);
        int listNum2 = list.size() - maxIndex + minIndex;

        List<DrawEntity> list1 = new ArrayList<>();
        for (int i = minIndex; i <= maxIndex; i++) {
            list1.add(list.get(i));
        }


        List<DrawEntity> list2 = new ArrayList<>();
        for (int i = maxIndex; i <= maxIndex + listNum2; i++) {
            list2.add(list.get(i % list.size()));
        }
        if (fission.getFissionPoints().size() == 1) {
            Point point = fission.getFissionPoints().get(0);
            list1.set(0, list1.get(0).reduce(point, true));
            list2.set(0, list2.get(0).reduce(point, true));
            list1.set(list1.size() - 1, list1.get(list1.size() - 1).reduce(point, false));
            list2.set(list2.size() - 1, list2.get(list2.size() - 1).reduce(point, false));
        } else {// fission.getFissionPoints().size() == 2
            Point target = list1.get(0).getEndPoint();
            Point closeList1Point, closeList2Point;
            if (fission.getFissionPoints().get(0).getDistance(target) < fission.getFissionPoints().get(1).getDistance(target)) {
                closeList1Point = fission.getFissionPoints().get(0);
                closeList2Point = fission.getFissionPoints().get(1);
            } else {
                closeList1Point = fission.getFissionPoints().get(1);
                closeList2Point = fission.getFissionPoints().get(0);
            }
            list1.set(0, list1.get(0).reduce(closeList1Point, true));
            list1.set(list1.size() - 1, list1.get(list1.size() - 1).reduce(closeList1Point, false));
            list2.set(0, list2.get(0).reduce(closeList2Point, true));
            list2.set(list2.size() - 1, list2.get(list2.size() - 1).reduce(closeList2Point, false));
        }

        result.add(list1);
        result.add(list2);


        return result;
    }

    public boolean pointWithinEntity(Point point) {
        int count = 0;//射线与多边形交点个数
        double x0 = point.getX();//point 坐标  (x0,y0)
        double y0 = point.getY();//point 坐标 , 取射线 y = y0 （x0 <= x)
//      随机射线 ax+by+c=0 x>x0
        double k = Math.random() * 100 + 1;
        double a = k;
        double b = -1;
        double c = y0 - k * x0;

        for (DrawEntity item : entitiesList) {
            switch (item.getType()) {
                case DrawEntity.LINE://line
                    double x1 = ((Line) item).getStartPoint().getX();
                    double y1 = ((Line) item).getStartPoint().getY();
                    double x2 = ((Line) item).getEndPoint().getX();
                    double y2 = ((Line) item).getEndPoint().getY();
                    double A = y2 - y1;//line Ax+By+C=0 (x1 <= x <= x2)
                    double B = x1 - x2;
                    double C = x2 * y1 - x1 * y2;
                    if (Math.abs(A * x0 + B * y0 + C) <= DrawUtil.Error && (x1 <= x0 && x0 <= x2 || x2 <= x0 && x0 <= x1) && (y1 <= y0 && y0 <= y2 || y2 <= y0 && y0 <= y1))
                        return false;//点（x0,y0)在线段上
                    if (Math.abs(k + A / B) <= DrawUtil.Error) {//当两直线平行
                        if (Math.abs(b + C / B) <= DrawUtil.Error) count++;
                        break;
                    }
                    double y = -(A * c - a * C) / (A * b - a * B);//两直线交点（x,y),再判断此点是否在line上
                    double x = Math.abs(A) > DrawUtil.Error ? -(B * y + C) / A : -(b * y + c) / a;
                    if ((x1 - DrawUtil.Error <= x && x <= x2 + DrawUtil.Error || x2 - DrawUtil.Error <= x && x <= x1 + DrawUtil.Error) && x0 <= x && (y1 - DrawUtil.Error <= y && y <= y2 + DrawUtil.Error || y2 - DrawUtil.Error <= y && y <= y1 + DrawUtil.Error))
                        count++;
                    break;
                case DrawEntity.ARC://Arc
                    Arc arc = ((Arc) item);
                    if (arc.isInnerPoint(point)) return false;//判断点是否在圆弧上
                    //        弧线arc
                    double c1 = arc.getCentre().getX();
                    double c2 = arc.getCentre().getY();
                    double r = arc.getRadius();
                    double q, w, e, delta;
                    q = (1 + Math.pow(a / b, 2));
                    w = 2 * a * c / Math.pow(b, 2) + 2 * a * c2 / b - 2 * c1;
                    e = Math.pow(c1, 2) + Math.pow(c2, 2) - Math.pow(r, 2) + Math.pow(c, 2) / Math.pow(b, 2) + 2 * c2 * c / b;
                    delta = Math.pow(w, 2) - 4 * q * e;
                    if (delta < 0) break;//无交点
                    else {
                        double x_1 = (-w + Math.sqrt(delta)) / (2 * q);
                        double x_2 = (-w - Math.sqrt(delta)) / (2 * q);
                        double y_1 = -(a * x_1 + c) / b;
                        double y_2 = -(a * x_2 + c) / b;
                        boolean xy1 = arc.isInnerPoint(new Point(x_1, y_1)) && point.getX() <= x_1;
                        boolean xy2 = arc.isInnerPoint(new Point(x_2, y_2)) && point.getX() <= x_2;
                        if (xy1 && xy2)
                            count += Math.abs(y_1 - y_2) <= DrawUtil.Error ? 0 : 2;
                        else if (xy1 || xy2)
                            count++;
                        else
                            count += 0;
                    }
                    break;
            }
        }
        return count % 2 == 1;
    }


    public List<DrawEntity> getEntitiesList() {
        return entitiesList;
    }

    public void setEntitiesList(List<DrawEntity> entitiesList) {
        this.entitiesList = entitiesList;
    }

    class FissionPoint {
        private int ID1;
        private int ID2;
        private List<Point> fissionPoints;

        public FissionPoint(int ID1, int ID2, List<Point> fissionPoints) {
            this.ID1 = ID1;
            this.ID2 = ID2;
            this.fissionPoints = fissionPoints;
        }

        public int getID1() {
            return ID1;
        }

        public int getID2() {
            return ID2;
        }

        public List<Point> getFissionPoints() {
            return fissionPoints;
        }
    }
}
